#include "cache.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>

static centry *cache_buf;

void dump_cache () {

  int i;

  debug ("CACHE DUMP\n--------------------------\n");
  debug ("S.NO.\t ID\t Dirty\t Last\n");
  for (i=0; i<CACHE_COUNT; i++) {
    object_id id = cache_buf[i].id;
    debug ("(%d) %d-%s %s-%s %d %d.%d\n", i, id.type, id.id, 
                  (id.key).key, (id.key).iv, cache_buf[i].is_dirty, 
                  cache_buf[i].last_access, cache_buf[i].last_access_us);

  }
  debug ("--------------------------\n");
}


void init_cache () {
  cache_buf = (centry *) malloc (sizeof (centry)*CACHE_COUNT);
  // TODO: initialize all the cache with directory object */

  int i;
  for (i=0; i<CACHE_COUNT; i++) {
    memset (cache_buf+i, 0, sizeof (centry)); 
    /* Make sure that the default never is a valid object */
    cache_buf[i].id.type = DATA_OBJ;
    memset (cache_buf[i].id.id, '0', IDLEN);
    cache_buf[i].is_dirty = 0;    
  }
}

void destroy_cache () {
  flush_all_cache ();
  free (cache_buf);
}

void flush_all_cache () {
  int i;
  for (i=0; i<CACHE_COUNT; i++) {
    if (cache_buf[i].is_dirty != 0) {
      /* We got a dirty entry, write it back, and mark it clean */
      put_object (&(cache_buf[i].id), &(cache_buf[i].obj));
      cache_buf[i].is_dirty = 0;
    }
  }

}

int get_object_c (object_id *id, object *obj,
                          size_t max_length, char *buf) {

  debug ("GET(C): %d-%s\n", id->type, id->id);

  /* If the version information is present, life is difficult. And we 
     need to check whether this is the correct thing */

  int i;

  for (i=0; i<CACHE_COUNT; i++) {

    /* Check whether this object is the one we are looking for */
    if (cache_buf[i].id.type == id->type && 
          (strncmp (cache_buf[i].id.id, id->id, IDLEN) == 0)) {

      if (id->ver != 0) {   /* Specific revision request */
        /* We write the cached entry, and bypass the remaining stuff */
        put_object (&(cache_buf[i].id), &(cache_buf[i].obj));
        cache_buf[i].is_dirty = 0;
        return get_object (id, obj, max_length, buf);
      }

      /* We have the object in the cache. Just copy it, update the 
         last used time, and return */
      obj->type = cache_buf[i].obj.type;
      obj->length = cache_buf[i].obj.length;
      if (obj->length > max_length)
        return -1;
      obj->data = buf;
      memcpy (buf, cache_buf[i].obj_buf, obj->length);

      struct timeval tv;
      gettimeofday (&tv, NULL);
      cache_buf[i].last_access = (int) tv.tv_sec;
      cache_buf[i].last_access_us = (int) tv.tv_usec;

      return (obj->length);
    }

    /* This is not the object requested. Just keep going on */
  }
    
  /* The object was not there in the cache. So, let's find the LRU object,
     write it back if it is dirty, bring the object requested from the
     disk, and put it in the cache, and then do some magic 
  */
  int lru_object = 0;

  for (i=0; i<CACHE_COUNT; i++) {
    if (cache_buf[lru_object].last_access > cache_buf[i].last_access)
      lru_object = i;
    else if (cache_buf[lru_object].last_access == cache_buf[i].last_access &&
          cache_buf[lru_object].last_access_us > cache_buf[i].last_access_us)
      lru_object = i;
  }

  /* This page is to be evicted, check if dirty */
  if (cache_buf[lru_object].is_dirty != 0) {
    put_object (&(cache_buf[lru_object].id), &(cache_buf[lru_object].obj));
    /* We don't need to, but let's just mark it undirty anyways*/
    cache_buf[lru_object].is_dirty = 0;
  }

  /* Fetch the object, put it in the cache */
  memcpy (&(cache_buf[lru_object].id), id, sizeof (object_id));
  get_object (id, &(cache_buf[lru_object].obj), 
              MAX_DATA_OBJ, cache_buf[lru_object].obj_buf);

  cache_buf[lru_object].is_dirty = 0;
  struct timeval tv;
  gettimeofday (&tv, NULL);
  cache_buf[lru_object].last_access = (int) tv.tv_sec;
  cache_buf[lru_object].last_access_us = (int) tv.tv_usec;

  memcpy (obj, &(cache_buf[lru_object].obj), sizeof (object));
  memcpy (buf, &(cache_buf[lru_object].obj_buf), obj->length);

  return obj->length;
}


void put_object_c (object_id *id, object *obj) {

  debug ("PUT(C): %d-%s\n", id->type, id->id);

  /* If the object is in the cache, just simply write to it. 
     Otherwise we need to do some eviction as well */

  int i;

  for (i=0; i<CACHE_COUNT; i++) {

    /* Check whether this object is the one we are looking for */
    if (cache_buf[i].id.type == id->type && 
          (strncmp (cache_buf[i].id.id, id->id, IDLEN) == 0)) {


      /* We have the object in the cache. Just copy it, update the 
         last used time, and return */
      cache_buf[i].obj.type = obj->type;
      cache_buf[i].obj.length = obj->length;
      cache_buf[i].obj.data = cache_buf[i].obj_buf;

      memcpy (cache_buf[i].obj_buf, obj->data, obj->length);

      struct timeval tv;
      gettimeofday (&tv, NULL);
      cache_buf[i].last_access = (int) tv.tv_sec;
      cache_buf[i].last_access_us = (int) tv.tv_usec;
      cache_buf[i].is_dirty = 1;

      return;
    }

    /* This is not the object requested. Just keep going on */
  }
    
  int lru_object = 0;

  for (i=0; i<CACHE_COUNT; i++) {
    if (cache_buf[lru_object].last_access > cache_buf[i].last_access)
      lru_object = i;
    else if (cache_buf[lru_object].last_access == cache_buf[i].last_access &&
      cache_buf[lru_object].last_access_us > cache_buf[i].last_access_us)
      lru_object = i;
  }

  /* This page is to be evicted, check if dirty */
  if (cache_buf[lru_object].is_dirty != 0) {
    put_object (&(cache_buf[lru_object].id), &(cache_buf[lru_object].obj));
    /* We don't need to, but let's just mark it undirty anyways*/
    cache_buf[lru_object].is_dirty = 0;
  }

  /* put it in the cache */
  memcpy (&(cache_buf[lru_object].id), id, sizeof (object_id));
  cache_buf[lru_object].is_dirty = 1;
  struct timeval tv;
  gettimeofday (&tv, NULL);
  cache_buf[lru_object].last_access = (int) tv.tv_sec;
  cache_buf[lru_object].last_access_us = (int) tv.tv_usec;

  memcpy (&(cache_buf[lru_object].obj), obj, sizeof (object));
  memcpy (&(cache_buf[lru_object].obj_buf), obj->data, obj->length);
  cache_buf[lru_object].obj.data = cache_buf[lru_object].obj_buf;
}


void flush_object (object_id *id) {

  int i;
  for (i=0; i<CACHE_COUNT; i++) {
    
    /* Is this the object we are looking for? */
    if (cache_buf[i].id.type == id->type && 
          (strncmp (cache_buf[i].id.id, id->id, IDLEN) == 0)) {

      if (cache_buf[i].is_dirty == 1) {
        put_object (&(cache_buf[i].id), &(cache_buf[i].obj));
        cache_buf[i].is_dirty = 0;
      }      

      break;
    }
  }
}


